package iModel;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Collection;
import org.eclipse.epsilon.eol.exceptions.EolRuntimeException;
import org.eclipse.epsilon.eol.exceptions.models.EolEnumerationValueNotFoundException;
import org.eclipse.epsilon.eol.exceptions.models.EolModelElementTypeNotFoundException;
import org.eclipse.epsilon.eol.exceptions.models.EolModelLoadingException;
import org.eclipse.epsilon.eol.exceptions.models.EolNotInstantiableModelElementTypeException;
import org.eclipse.epsilon.eol.execute.introspection.AbstractPropertyGetter;
import org.eclipse.epsilon.eol.execute.introspection.AbstractPropertySetter;
import org.eclipse.epsilon.eol.execute.introspection.IPropertyGetter;
import org.eclipse.epsilon.eol.execute.introspection.IPropertySetter;
import org.eclipse.epsilon.eol.models.Model;

import dbConnection.SQL2012Connection;

public class DBReflexiveModel extends Model {
	
	private static final boolean debug = false;  //This is so we can implement error messaging for debugging. This will be removed from final version.
	
	protected ArrayList<DBReflexiveModelElement> contents = new ArrayList<DBReflexiveModelElement>();
	
	@Override
	public void load() throws EolModelLoadingException
	{
	/****************************************************************************************************************************
	 * This connects to the database and loops around each of the tables and creates a new ModelElement for each of the tables **
	 * 	                                                                                                                       **
	 ***************************************************************************************************************************/		
		//Connect to the database
	    SQL2012Connection myDbTest = new SQL2012Connection("jdbc:sqlserver://","localhost","5551","epsilon","epsilon","epsilon","cursor");  //connects to a database called epsilon with a user called epsilon with a password called epsilon.
		//Get a list of all the tables in the array.
		String[] arrTables = myDbTest.getSQLTables();  		    
		//For each table we need to loop around and add them to the model.	
		 for(int i=1; i < arrTables.length;i++ )
		  {
			  if(arrTables[i] !=null)
			  {
				  //Split data into an array and add to the epsilon Model.
				  String[] objmap = arrTables[i].toString().split("\\.");  //. needs to be escaped as its a special character.  
				  if(isInstantiable(objmap[2])==true)
				  {
					  contents.add(new DBReflexiveModelElement(objmap[2]));    
				  }
				  else
				  {
					System.out.println("Warning cannot create object called ".concat(objmap[2]));  
				  }
				  
			  }
		   }
	}
	
	@Override
	//Not Applicable for this implementation
	public Object getEnumerationValue(String enumeration, String label)
	    throws EolEnumerationValueNotFoundException {
		System.out.println("getEnumerationValue");
		return null;
	}

	@Override
	//Return a model element for each row in the database, this shouldnt really be needed for this implementation
	public Collection<?> allContents() 
	{
		System.out.println("allContents");
		return contents;
	}

	@Override
	//bring back a list of model elements which belong to an object..
	public Collection<?> getAllOfType(String type) throws EolModelElementTypeNotFoundException 
	{
		System.out.println("getAllOfType");
		//Create a new array List for stored objects.
		ArrayList<DBReflexiveModelElement> result = new ArrayList<DBReflexiveModelElement>();
		
		//Now here again im a little confused as the Objects are now called PK_blah we will never find associated objects for the type DatabaseLog
		//So looks like im wrong to add the new ModelElements as a different Type mmmmmmmmmmmm.
		
		for (DBReflexiveModelElement e : contents) 
		{	
		   if (e.getType().equals(type)) 
			{
				result.add(e);
			}
		}
        //n.b. since the ModelElement is in a hash map we cannot be guaranteed the ordering of the data so be careful here.
		//System.out.println("There are ".concat(Integer.toString(result.size())).concat(" in the results set"));
		
		System.out.println(result.get(0).props.toString());
		
		
		
		return result;
	}

	@Override
	//bring back select * from ObjectName bring back a list of rows from a table.
	public Collection<?> getAllOfKind(String type)
			throws EolModelElementTypeNotFoundException 
			{
		System.out.println("getAllOfKind");
		//get all the column names for the particular object.
		SQL2012Connection myDbTest = new SQL2012Connection("jdbc:sqlserver://","localhost","5551","epsilon","epsilon","epsilon","cursor");  //connects to a database called epsilon with a user called epsilon with a password called epsilon.
		ArrayList<String> arrCols = myDbTest.getSQLColumns(type);
		
        //Execute against the database to bring back the rows in the table..	
		try {
		   Connection con = myDbTest.getConnection();
		   Statement st = con.createStatement();  
		   ResultSet rs = st.executeQuery("SELECT ".concat(myDbTest.getcolumnstring(arrCols)).concat(" FROM ").concat(type)); 
		
		   //Now we need to get the Primary Key of a table if there is not a PK then we need to return a composite PK for all columns;
		   ArrayList<String> PKCols = myDbTest.getPrimaryKey(type);
		  
		   //Now we have the results set we need to loop around and add the elements to the model.
		   StringBuilder sb = new StringBuilder(); 
		   System.out.println("Creating Row Elements......");
		   while (rs.next())
		   {
			sb.delete(0, sb.length());
			sb.append(type.concat("_"));  //This adds the tablename to the string we shouldnt really need this as it should be stored within the Hash table within the object called tablename.
			   //Compose the Primary Key Alias for storage this will add all the PK columns together if no PK is found then it will concatonate all the results in the row to make a unique key as possible.
			   for(int z=0;z< PKCols.size();z++)
			   {
				   sb.append(rs.getObject(PKCols.get(z)).toString());
					if(z < PKCols.size()-1)
					{
						sb.append("_"); 
					} 
			   }
			   
			   //A Little Confused here about how to treat Rows and how to associate them with their table.		   
			   //Create a new element Object called tablename_PK  ??? is this the correct way to implement this.
			   DBReflexiveModelElement rowelement = new DBReflexiveModelElement(sb.toString()); 
			   for (int i=1; i < arrCols.size(); i++)
			   {
				 //Should this be a new DBRefeflexiveModelElement ???? or should we just be simply putting in the the values to the hash map here for the table object.
				   //e.g  PK_COLUMN = Value for example 1234_Name = "Testing" so 1234 is the PK for that particular row.
				   rowelement.put(arrCols.get(i).toString(), rs.getObject(arrCols.get(i).toString()));
				   //If so then we dont really need the TableName in the PK we only need the PK columns but lets check this out further.
				   //contents.add(new DBReflexiveModelElement(type).put(sb.toString().concat(arrCols.get(i).toString()), rs.getObject(arrCols.get(i).toString())));
			         
			   } 
		   }
		} 
		catch (SQLException e)
		{
			//Catch the SQL Exceptions which may be thrown.
			e.printStackTrace();
		} 
		finally
		{
			//Here we may need to look at closing off the connection and removing all elements from the associated object.
		}
		return getAllOfType(type);		
	}

	@Override
	//This should return DBReflexiveModelElement.class.
	public Object getTypeOf(Object instance) 
	{
		System.out.println("getTypeOf");
		return instance.getClass();
	}

	@Override
	//DBRelflexiveModel should return the name of the table which the row comes from.
	public String getTypeNameOf(Object instance) 
	{
		System.out.println("getTypeNameOf");
		return ((DBReflexiveModelElement) instance).getType();
	}

	@Override
	//Create a new row of the table specied.
	public Object createInstance(String type)
			throws EolModelElementTypeNotFoundException,
			EolNotInstantiableModelElementTypeException {
		System.out.println("createInstance");
		DBReflexiveModelElement newElement = new DBReflexiveModelElement(type);
		//We need to be careful here because we can insert rows into the table but database may require non null values for certain columns..We will have to deal with this by adding in default values and then
		//updating the columns with the correct values.
		contents.add(newElement);
		return newElement;
	}

	@Override
	//String id is the tablename_PK of the database table
	public Object getElementById(String id) 
	{
		System.out.println("getElementById");
		return null;
	}

	@Override
	//Checks to see the value for the PK if no PK is implemented then return null.
	public String getElementId(Object instance) 
	{
		
		System.out.println("getElementId");
		return null;
	}

	@Override
	//This should not be implemented as we must use the PK assignment from the database so you are not allowed to set the PK Value
	public void setElementId(Object instance, String newId) {
		System.out.println("setElementId");
		System.out.println("Warning: You cannot set the Primary Key for the Object");
	}

	@Override
	//Delete Row from Database.
	public void deleteElement(Object instance) throws EolRuntimeException {
		System.out.println("deleteElement");
		/************************************************************************************
		 * Work In progress
		 ***********************************************************************************/
		//We need to check of the object that is passed to the method is the PK for the table if not we cannot delete the data.
		
		//Create a new string builder
		
		//Start the transaction
		//DELETE FROM Object where get the PK list we need to build up this statement dynamically based on what the columns for the PK are if it has a composite PK as the PK in the
		//Object which is passed is a surrogate PK and does not exists withing the database iteself.
		
	    //Either commit or rollback the transaction here.
		//Once this has been deleted then we are OK, if not we need to rollback the transaction
		
		
		
		/************************************************************************************
		 *  End of Work in Progress
		 ***********************************************************************************/
		
		//Remove the contents from the Object.
		contents.remove(instance);
	}

	@Override
	//Owns will check if a model owns an object.  What exactly is owns supposed to do ???? cant memember.
	public boolean owns(Object instance) 
	{
		if(contents.contains(instance)==true){System.out.println("Object is owned by the model");}else {System.out.println("Object is not owned by the model");};
		return contents.contains(instance);
	}

	@Override
	
	//returns true look at many to many relationships here...
	public boolean isInstantiable(String type)
	{	
		    if(debug==true)
			{
			System.out.println("We can create object called ".concat(type).toString());
			}
		return true;
	}

	@Override
	//Checks to see of it is a DBReflexiveModelElement owns superceeds this..
	public boolean isModelElement(Object instance) {
		System.out.println("isModelElement");
		return true;
	}

	@Override
	//If there is a table of that name in the model if there is then it returns true of it does not it returns false.
	public boolean hasType(String type) {
		
		for (DBReflexiveModelElement e : contents) {
			if (type.equals(e.getType()))	
				System.out.println("Table named ".concat(type).concat(" exists in the model"));
				return true;
		}
		System.out.println("Warning : Table named ".concat(type).concat(" does not exists in the model"));
		return false;
	}

	@Override
	//This does not need to do anything as we are not creating tables in the datababase although that could be one for the future?
	public boolean store(String location) {
		System.out.println("location");
		return false;
	}

	@Override
	//This does not need to do anything as we are not creating tables in the datababase although that could be one for the future?
	public boolean store() 
	{
		System.out.println("store");
		// TODO Auto-generated method stub
		return false;
	}
	
	@Override
	//returns implementation of iPropertyGetter.
	public IPropertyGetter getPropertyGetter() 
	{
		System.out.println("getPropertyGetter");
		return new ElementPropertyGetter();
	}
	
	@Override
	//Setting the 
	public IPropertySetter getPropertySetter() {
		System.out.println("getPropertySetter");
		return new ElementPropertySetter();
	}
	
	class ElementPropertyGetter extends AbstractPropertyGetter {
		
		@Override
		//Gets Model Element and Column Name.
		public Object invoke(Object object, String property)
				throws EolRuntimeException {
			System.out.println("invoke");
			return ((DBReflexiveModelElement) object).getProps().get(property);
		}
		
	}
	
	class ElementPropertySetter extends AbstractPropertySetter {
       //This is for an update to the database.
		@Override
		public void invoke(Object value) throws EolRuntimeException {
			System.out.println("invoke");
			//We need to find the PK for the relevant table and then update the database with the result from the Object.			
			((DBReflexiveModelElement) object).getProps().put(property, value);	
		}	
	}
}




